﻿/* 
 * Enterprise Library Extensions - DI + ORM.
 * Copyright (C) 2006
 *
 * GNU General Public License
 *
 * This program is free software; you can redistribute 
 * it and/or modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation; either version 2 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 */
#region Using directives

using System;
using System.Text;
using System.Reflection;
using ELE.EnterpriseLibrary.Naming.Configuration;
using ELE.Common.Util;
using ELE.EnterpriseLibrary.Common.Logging;

#endregion

namespace ELE.EnterpriseLibrary.Naming.Factories
{
  /// <summary>
  /// General component factory methods
  /// </summary>
	public class ComponentObjectFactory
	{
    /// <summary>
    /// Look for a properly defined constructor from a configuration object
    /// </summary>
    /// <param name="conf"></param>
    /// <returns></returns>
    public static ConstructorInfo GetProperConstructor(ComponentConfiguration conf)
    {
      if (conf == null || string.IsNullOrEmpty(conf.ComponentType.ClassName)) return null;
      try
      {
        Type classType = null;
        if (StringUtils.NotEmpty(conf.ComponentType.AssemblyName))
        {
          Assembly classAssembly = Assembly.Load(conf.ComponentType.AssemblyName);
          classType = classAssembly.GetType(conf.ComponentType.ClassName);
        }
        else
        {
          classType = Type.GetType(conf.ComponentType.ClassName);
        }
        if (classType == null)
        {
          return null;
        }
        // Get all constructors for the named class
        ConstructorInfo[] constructors = classType.GetConstructors();

        int i, j;
        ConstructorInfo constructorToUse = null;

        // Iterate over each constructor, determining a.) whether we
        // can use it and b.) how attractive it is to us.
        // Constructors are ranked based on their parameters, and we
        // use the highest rank to actually construct the object.
        for (i = 0; i < constructors.Length; i++)
        {
          ParameterInfo[] parms = constructors[i].GetParameters();
          if ((conf.ConstructorArguments == null || conf.ConstructorArguments.Count <= 0))
          {
            if (parms.Length <= 0)
            {
              constructorToUse = constructors[i];
              break;
            }
          }
          else if (parms.Length == conf.ConstructorArguments.Count)
          {
            bool[] matches = new bool[parms.Length];
            // make sure they are all false
            for (int g = 0; g < matches.Length; g++)
            {
              matches[g] = false;
            }
            for (j = 0; j < parms.Length; j++)
            {
              String name = parms[j].Name;
              for (int k = 0; k < conf.ConstructorArguments.Count; k++)
              {
                if (conf.ConstructorArguments[k].Name.Equals(name))
                {
                  matches[j] = true;
                  break;
                }
              }
            }
            bool thisIsIt = matches[0];
            if (matches.Length > 1)
            {
              for (int g = 1; g < matches.Length; g++)
              {
                thisIsIt = thisIsIt && matches[g];
              }
            }
            if (thisIsIt)
            {
              constructorToUse = constructors[i];
              break;
            }
          }
        }

        return constructorToUse;
      }
      catch (Exception e)
      {
        throw e;
      }
    }
    /// <summary>
    /// Get proper factory method to use for initializing object
    /// </summary>
    /// <param name="conf"></param>
    /// <returns></returns>
    public static MethodInfo GetProperFactory(ComponentConfiguration conf)
    {
      if (conf == null || conf.Factory == null || string.IsNullOrEmpty(conf.Factory.FactoryType.ClassName)) return null;
      try
      {
        Type classType = null;
        if (StringUtils.NotEmpty(conf.Factory.FactoryType.AssemblyName))
        {
          Assembly classAssembly = Assembly.Load(conf.Factory.FactoryType.AssemblyName);
          classType = classAssembly.GetType(conf.Factory.FactoryType.ClassName);
        }
        else
        {
          classType = Type.GetType(conf.Factory.FactoryType.ClassName);
        }
        if (classType == null)
        {
          return null;
        }
        // Get all constructors for the named class
        MethodInfo[] methods = classType.GetMethods();

        int i, j;
        MethodInfo methodToUse = null;

        // Iterate over each constructor, determining a.) whether we
        // can use it and b.) how attractive it is to us.
        // Constructors are ranked based on their parameters, and we
        // use the highest rank to actually construct the object.
        for (i = 0; i < methods.Length; i++)
        {
          MethodInfo method = methods[i];
          if (!method.IsStatic || !method.IsPublic)
          {
            continue;
          }
          ParameterInfo[] parms = method.GetParameters();
          if ((conf.Factory.FactoryMethodParameters == null || conf.Factory.FactoryMethodParameters.Count <= 0))
          {
            if (parms.Length <= 0)
            {
              methodToUse = method;
              break;
            }
          }
          else if (parms.Length == conf.Factory.FactoryMethodParameters.Count)
          {
            bool[] matches = new bool[parms.Length];
            // make sure they are all false
            for (int g = 0; g < matches.Length; g++)
            {
              matches[g] = false;
            }
            for (j = 0; j < parms.Length; j++)
            {
              String name = parms[j].Name;
              for (int k = 0; k < conf.Factory.FactoryMethodParameters.Count; k++)
              {
                if (conf.Factory.FactoryMethodParameters[k].Name.Equals(name))
                {
                  matches[j] = true;
                  break;
                }
              }
            }
            bool thisIsIt = matches[0];
            if (matches.Length > 1)
            {
              for (int g = 1; g < matches.Length; g++)
              {
                thisIsIt = thisIsIt && matches[g];
              }
            }
            if (thisIsIt)
            {
              methodToUse = method;
              break;
            }
          }
        }

        return methodToUse;
      }
      catch (Exception e)
      {
        throw e;
      }
    }
    /// <summary>
    /// Gets an object from a component initializer
    /// </summary>
    /// <param name="args"></param>
    /// <param name="ctx"></param>
    /// <returns></returns>
    public static object GetReferencedObjectFromComponentInitializer(ComponentInitializer args, INamingContext ctx)
    {
      CompositeNameParser np = new CompositeNameParser();
      string componentName = (string)args.Value;
      string propertyName = null;
      if (StringUtils.Contains(componentName,CompositeNameParser.NAME_DELIMITER))
      {
        propertyName = componentName.Substring(componentName.IndexOf(CompositeNameParser.NAME_DELIMITER));
      }
      else if (StringUtils.Contains(componentName,CompositeNameParser.SECTION_DELIMITER))
      {
        propertyName = componentName.Substring(componentName.LastIndexOf(CompositeNameParser.SECTION_DELIMITER) + 1);
      }
      else
      {
        propertyName = componentName;
      }
      if (StringUtils.Contains(propertyName,"."))
      {
        // need fix.
        propertyName = propertyName.Substring(propertyName.IndexOf(".") + 1);
        componentName = componentName.Substring(0, componentName.Length - propertyName.Length - 1);
      }
      else
      {
        propertyName = null;
      }
      IName argComponentName = np.Parse(componentName);

      // This will throw a CircularReferenceException when a reference to an in process component  is made
      // It can also throw a ResourceInitializationException if the referenced component can not be initialized
      object argObject = null;
      try
      {
        argObject = ctx.Lookup(argComponentName);
      }
      catch(ResourceInitializationException rie)
      {
        ctx.Log("ONDI ; ComponentObjectFactory ; Referenced resource named " + argComponentName + " could not be initialized, setting value to null", rie, LogLevel.Warning);
        throw rie;
      }

      if (argObject != null && propertyName != null)
      {
        PropertyInfo prop = argObject.GetType().GetProperty(propertyName);
        if (prop != null && prop.CanRead)
        {
          return prop.GetValue(argObject, null);
        }
      }
      return argObject;
    }
    /// <summary>
    /// Gets a component name from a reference string
    /// <para>
    /// For example: a reference string like "http://app/file.ext:/name/of/component.property" will
    /// yield "/name/of/component" when passed into this method.
    /// </para>
    /// </summary>
    /// <param name="reference">The complete reference name</param>
    /// <returns>The name of the component</returns>
    public static string GetComponentNameFromReference(string reference)
    {
      if (StringUtils.Contains(reference,CompositeNameParser.SECTION_DELIMITER))
      {
        reference = reference.Substring(reference.LastIndexOf(CompositeNameParser.SECTION_DELIMITER) + 1);
      }
      if (StringUtils.Contains(reference,"."))
      {
        reference = reference.Substring(0, reference.IndexOf("."));
      }
      return reference;
    }
  }
}
